﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using WOWSharp.Community.ObjectModel;
using System.Data.Entity.Infrastructure;
using System.Data;

namespace AuctionHouseDataLibrary
{
    public class BackgroundDownloadThread
    {
        private ReaderWriterLockSlim _queueLock = new ReaderWriterLockSlim();
        private bool _running = true;
        private Queue<ItemToLoad> _itemsToLoad = new Queue<ItemToLoad>();
        private int _remainingToProcess;

        private int _runningWorkerThreads;
        private ReaderWriterLockSlim _runningWorkerThreadsLock = new ReaderWriterLockSlim();
        
        public void IncrementRunningWorkerThreads()
        {
            _runningWorkerThreadsLock.EnterWriteLock();
            try
            {
                _runningWorkerThreads++;
            }
            finally
            {
                _runningWorkerThreadsLock.ExitWriteLock();
            }
        }
        public void DecrementRunningWorkerThreads()
        {
            _runningWorkerThreadsLock.EnterWriteLock();
            try
            {
                _runningWorkerThreads--;
            }
            finally
            {
                _runningWorkerThreadsLock.ExitWriteLock();
            }
        }
        public int RunningWorkerThreads
        {
            get
            {
                int returnMe = 0;
                _runningWorkerThreadsLock.EnterReadLock();
                try
                {
                    returnMe = _runningWorkerThreads;
                }
                finally
                {
                    _runningWorkerThreadsLock.ExitReadLock();
                }
                return returnMe;
            }
        }

        public void Run()
        {
            while (_running)
            {
                bool process = false;
                bool waitlong = false;
                _queueLock.EnterReadLock();
                try
                {
                    if (_remainingToProcess > 0)
                    {
                        process = true;
                    }
                }
                finally
                {
                    _queueLock.ExitReadLock();
                }
                if (process)
                {
                    ProcessList();   
                }
                else
                {
                    int itemCount = CheckProcessNonupdatedItems();
                    if (itemCount == 0)
                        waitlong = true;
                }
                if (waitlong)
                {
                    Thread.Sleep(5000);
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
        }

        private int CheckProcessNonupdatedItems()
        {
            using (var context = new ItemDatabaseContext())
            {
                var itemList = context.Items.Where(p => p.DataUpdated == false).Select(p => p.ItemId).ToList();
                if (itemList.Count > 0)
                {
                    QueueItemUpdateList(itemList);
                }
                return itemList.Count;
            }
        }

        public void QueueThreadedUpdate(object itemIdObject)
        {
            try
            {
                ItemToLoad itemToLoad = (ItemToLoad)itemIdObject;
                switch (itemToLoad.ItemType)
                {
                    case ItemType.Image:
                        DownloadItem(itemToLoad.ItemId);
                        break;
                    case ItemType.Auction:
                        UpdateAuction(itemToLoad.Auction, itemToLoad.ScanEventId);
                        break;
                    default:
                        break;
                }
            }
            finally
            {
                DecrementRunningWorkerThreads();
            }
        }

        private void UpdateAuction(Auction auction, int scanEventId)
        {
            CheckAddItem(auction.ItemId);
            AddNewAuction(auction);
            AddCurrentPrices(auction, scanEventId);
        }

        private void AddCurrentPrices(Auction auction, int scanEventId)
        {
            using (var context = new ItemDatabaseContext())
            {
                AuctionCurrentPrice addNewCurrentPrice = new AuctionCurrentPrice()
                    {
                        AuctionId = auction.AuctionId,
                        ScanEventId = scanEventId,
                        Price = auction.CurrentBidValue
                    };
                context.AuctionCurrentPrices.Add(addNewCurrentPrice);
                context.SaveChanges();
            }
        }

        private void AddNewAuction(Auction auction)
        {
            using (var context = new ItemDatabaseContext())
            {
                Auction checkAuction = context.Auctions.SingleOrDefault(p => p.AuctionId == auction.AuctionId);
                if (checkAuction == null)
                {
                    context.Auctions.Add(auction);
                    context.SaveChanges();
                }
            }
        }

        private void CheckAddItem(int itemId)
        {
            using (var context = new ItemDatabaseContext())
            {
                Item item = context.Items.SingleOrDefault(p => p.ItemId == itemId);
                if (item == null)
                {
                    context.Items.Add(Item.EmptyItem(itemId));
                    try
                    {
                        context.SaveChanges();
                    }
                    catch (DbUpdateException)
                    {
                        // Ignore this exception, this means another thread got to this item first
                    }
                }
            }
        }

        private void DownloadItem(int itemId)
        {
            using (var context = new ItemDatabaseContext())
            {
                // Add in the missing items now
                Item item = context.Items.SingleOrDefault(p => p.ItemId == itemId);
                if (item == null)
                {
                    item = new Item();
                    ItemMangement.GetItemFromWoWhead(itemId, item);
                    context.Items.Add(item);
                    context.SaveChanges();
                }
                else
                {
                    ItemMangement.GetItemFromWoWhead(itemId, item);
                    context.Entry(item).State = EntityState.Modified;
                    context.SaveChanges();
                }
                ItemMangement.CheckDownloadThumbnail(item.DisplayIcon);
            }
        }

        private void ProcessList()
        {
            var itemsToProcess = RemoveItemsFromQueue(100);
            foreach (var i in itemsToProcess)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(QueueThreadedUpdate), i);
                IncrementRunningWorkerThreads();
            }
            while (RunningWorkerThreads > 0)
                Thread.Sleep(100);
            _queueLock.EnterWriteLock();
            try
            {
                _remainingToProcess -= itemsToProcess.Count;
            }
            finally
            {
                _queueLock.ExitWriteLock();
            }
        }

        private List<ItemToLoad> RemoveItemsFromQueue(int count)
        {
            List<ItemToLoad> returnItems = new List<ItemToLoad>();
            _queueLock.EnterWriteLock();
            try
            {
                for (int i = 0; i < count; i++)
                {
                    if (_itemsToLoad.Count > 0)
                    {
                        returnItems.Add(_itemsToLoad.Dequeue());
                    }
                }
            }
            finally
            {
                _queueLock.ExitWriteLock();
            }
            return returnItems;
        }

        public void QueueAuctionUpdateList(List<Auction> auctions, int scanEventId)
        {
            _queueLock.EnterWriteLock();
            try
            {
                Globals.Logger.InfoFormat("{0} auctions added to queue for processing.", auctions.Count);
                foreach (var auction in auctions)
                {
                    _itemsToLoad.Enqueue(new ItemToLoad() { Auction = auction, ItemType = ItemType.Auction, ScanEventId = scanEventId});
                    _remainingToProcess++;
                }
            }
            finally
            {
                _queueLock.ExitWriteLock();
            }
        }

        public void QueueItemUpdateList(List<int> items)
        {
            _queueLock.EnterWriteLock();
            try
            {
                Globals.Logger.InfoFormat("{0} item updates added to queue for processing.", items.Count);
                foreach (var item in items)
                {
                    _itemsToLoad.Enqueue(new ItemToLoad() { ItemId = item, ItemType = ItemType.Image});
                    _remainingToProcess++;
                }
            }
            finally
            {
                _queueLock.ExitWriteLock();
            }
        }

        public void Stop()
        {
            _running = false;
        }

        public int ItemsInQueue()
        {
            int returnVal = 0;
            _queueLock.EnterReadLock();
            try
            {
                returnVal = _remainingToProcess;
            }
            finally
            {
                _queueLock.ExitReadLock();
            }
            return returnVal;
        }
    }
}